/* Copyright 2012 Ali H. Caliskan.  All rights reserved.

   This file is part of Sysmkr.

   Sysmkr is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package libmkr

import (
	"bufio"
	"database/sql"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"path/filepath"
	"os"
	"os/exec"
	"strings"
	_ "sqlite3"
)

var (
	BinDir = "/usr/bin"
	EtcDir = "/etc"
	LibDir = "/usr/lib"
	RunDir = "/run"
	ShrDir = "/usr/share"
	UsrDir = "/usr"
	VarDir = "/var"

	ImgDir = filepath.Join(os.Getenv("HOME"), ".image")
	BldDir = filepath.Join(os.Getenv("HOME"), ".build")
	RcsDir = filepath.Join(BldDir, "rcs")
	TmpDir = filepath.Join(BldDir, "tmp")

	RootDir = "/"
	SysDir = filepath.Join(RootDir, "system")
	SysBinDir = filepath.Join("/system/bin") // required for scripts to work
	SysPkgDir = filepath.Join(SysDir, "pkg")

	PkgFree = "pkgFree"
	PkgSync = "pkgSync"
	PkgFreeClean = "pkgFreeClean"
	PkgSyncSetup = "pkgSyncSetup"

	PkgMkrCfg = filepath.Join(EtcDir, "pkgmkr.cfg")
	RcsUrl = "http://sysrcs.googlecode.com/git/"

	LocalSQLite = filepath.Join(SysDir, "local.sqlite")
	GlobalSQLite = filepath.Join(SysPkgDir, "global.sqlite")
	RecipeSQLite = filepath.Join(RcsDir, "recipe.sqlite")
	RecipeSQLiteExt = "recipe.sqlite"

	RcsFileExt = ".recipe"
	SysFileExt = ".system"
	PkgFileExt = "pkg.txz"
)


func GetArg(v int) string {

	flag.Parse()
	return flag.Arg(v)
}


func GetArgs() []string {

	flag.Parse()
	return flag.Args()
}


func MkDir(dir string) {

	_, err := os.Stat(dir)
	if err != nil {
		os.Mkdir(dir, 0755)
	}
}


func RmDir(dir string) {

	args := []string{"-r", dir}
	Run("rm", args...)
}


func CheckError(err error) {
 
	if err != nil {
		log.Fatal(err)
	}
}


func Run(arg string, args ...string) {
	cmd := exec.Command(arg, args...)

	cmd.Stdin = os.Stdin 
	cmd.Stdout = os.Stdout 
	cmd.Stderr = os.Stderr 

	err := cmd.Run()
	CheckError(err)
}


func RunScript(name string, vers string, skip string, path string) {

	PkgDir := filepath.Join(TmpDir, "pkg", name)
	SrcDir := filepath.Join(TmpDir, "src", name)

	RcsFile := name + RcsFileExt
	ff := filepath.Join(RcsDir, name, RcsFile + ".in")
	os.Create(ff)

	_, err := os.Stat(filepath.Join(RcsDir, name, RcsFile))
	if err == nil {

		RcsFile = filepath.Join(RcsDir, name, RcsFile)

		fi, err := os.Open(RcsFile)
		CheckError(err)

		defer fi.Close()

		r := bufio.NewReader(fi)
		line, err := r.ReadString('\n')

		w, err := os.OpenFile(ff, os.O_WRONLY | os.O_APPEND | os.O_CREATE, 0777)
		CheckError(err)

		_, err = w.Write([]byte(""))
		CheckError(err)

		RcsDir = filepath.Join(RcsDir, name)

		for err == nil {
			line = strings.Replace(line, "%name", name, -1)
			line = strings.Replace(line, "%vers", vers, -1)
			line = strings.Replace(line, "%rcs", RcsDir, -1)
			line = strings.Replace(line, "%src", SrcDir, -1)
			line = strings.Replace(line, "%pkg", PkgDir, -1)
			line = strings.Replace(line, "%bin", BinDir, -1)
			line = strings.Replace(line, "%etc", EtcDir, -1)
			line = strings.Replace(line, "%lib", LibDir, -1)
			line = strings.Replace(line, "%run", RunDir, -1)
			line = strings.Replace(line, "%shr", ShrDir, -1)
			line = strings.Replace(line, "%usr", UsrDir, -1)
			line = strings.Replace(line, "%var", VarDir, -1)

			_, err = w.Write([]byte(line))
			CheckError(err)

			line, err = r.ReadString('\n')

		}

		defer w.Close()

		argsrc := []string{"-i", "-c", "set e; . " + ff + "; if [ \"$(type -t srcSetup)\" = 'function' ]; then srcSetup; fi"}
		args := []string{"bash", "-i", "-c", "set -e; . " + PkgMkrCfg + "; export CHOST CFLAGS CXXFLAGS LDFLAGS MAKEFLAGS; . " + ff + "; if [ \"$(type -t pkgSetup)\" = 'function' ]; then pkgSetup; fi"}

		if skip == "src" {
			os.Chdir(filepath.Join(SrcDir,path))
			Run("fakeroot", args...)
		} else {
			os.Chdir(filepath.Join(SrcDir,path))
			Run("bash", argsrc...)
			Run("fakeroot", args...)
		}
	}

	os.Remove(ff)

	os.Chdir(BldDir)
}


func BuildEnvDirs(name string) {

	PkgDir := filepath.Join(TmpDir, "pkg", name)
	SrcDir := filepath.Join(TmpDir, "src", name)

	DirList := map[string]string{RcsDir: "rcs",
				     SrcDir: "src",
			       	     PkgDir: "pkg",
			             TmpDir: "tmp"}

	for dir, _ := range DirList {

		_, err := os.Stat(dir)
		if err != nil {

			os.MkdirAll(dir, 0755)
		}
	}
}


func FetchRecipe() {

	fmt.Println("info: fetching recipe database:")

	RcsFile := filepath.Join(RcsDir, RecipeSQLiteExt)
	_, err := os.Stat(RcsFile)
	if err == nil {
		os.Remove(RcsFile)
	}

	rcsfile := RcsUrl + RecipeSQLiteExt
	args := []string{"-nv", "-P", RcsDir, rcsfile} 

	Run("wget", args...)

	fmt.Println("      done")
}


func FetchScript(name string) {

	RcsFile := filepath.Join(RcsDir, name, name + RcsFileExt)
	RcsUrlDir := RcsUrl + name

	_, err := os.Stat(RcsFile)
	if err != nil {

		os.Chdir(RcsDir)

		fmt.Println("info: fetching recipe:", name)

		args := []string{"-nv", "-r", "-l1", "-np", "-nH",
				"--cut-dirs=1", "--reject=html", RcsUrlDir}
	
		Run("wget", args...)

		fmt.Println("      done")

		os.Chdir(BldDir)
	}
}


func FmtSrcFile(name string,
		vers string,
		urls string) string {

	urls = strings.Replace(urls, "%name", name, -1)
	urls = strings.Replace(urls, "%vers", vers, -1)

	return urls
}


func DefSrcFile(name string, vers string) [2]string {

	url := GetInfoSrc(RecipeSQLite, name)[3]

	srcfile := FmtSrcFile(name, vers, url)

	file := strings.Split(srcfile, "/")
	file = file[len(file)-1:]

	return [2]string{srcfile, file[0]}
}


func GetSrcFile(name string, vers string) {

	os.Chdir(TmpDir)

	srcfile := DefSrcFile(name, vers)[0]
	file := DefSrcFile(name, vers)[1]

	_, err := os.Stat(filepath.Join(TmpDir, file))
	if err != nil {

		fmt.Println("info: fetching:", srcfile)

		args := []string{"-nv", srcfile} 
		Run("wget", args...)

	} else  {
		fmt.Println("     ", file, "exists")
	}
}


func ExtractFile(name string, vers string) {

	SrcDir := filepath.Join(TmpDir, "src", name)

	file := DefSrcFile(name, vers)[1]
	argTar := []string{"-C", SrcDir, "-xf", file}
	argZip := []string{"-d", SrcDir, file}

	fmt.Println("info: extracting:", file)

	if strings.HasSuffix(file, ".tar.xz") || strings.HasSuffix(file, ".tar.gz") {
		Run("tar", argTar...)

	} else if strings.HasSuffix(file, ".txz") || strings.HasSuffix(file, ".tgz") {
		Run("tar", argTar...)

	} else if strings.HasSuffix(file, ".tar.bz2") || strings.HasSuffix(file, ".tar") {
		Run("tar", argTar...)

	} else if strings.HasSuffix(file, ".zip") {
		Run("unzip", argZip...)

	} else {

		fmt.Println("     ", file, "unsupported file type")
	}
}


func ReadFile(file string) string {

	f, err := os.Open(file)
	CheckError(err)

	defer f.Close()

	r := bufio.NewReader(f)
	line, err := r.ReadString('\n')

	text := ""
	for err == nil {
		text += line
		line, err = r.ReadString('\n')
	}

	if err != io.EOF {
		fmt.Println(err)
	}

	return text
}


func WriteFileList(path string, f os.FileInfo, err error) error {

	FileList := "filelist"

	w, err := os.OpenFile(FileList, os.O_WRONLY | os.O_APPEND | os.O_CREATE, 0644)
	CheckError(err)

	_, err = w.Write([]byte(""))
	CheckError(err)

	if len(path) >= 2 && path != "filelist" { 

		path = filepath.Join("/", path)

		_, err = w.Write([]byte(path + "\n"))
		CheckError(err)
	}

	defer w.Close()

	return nil
}


func CheckFileLists(filelist string, name string) {

	FList := strings.Split(filelist, "\n")

	for _x, x := range GetPkgInfo("list") {

		FList2 := strings.Split(x, "\n")

		for _, y := range FList2 {

			for _, v := range FList {

				if _x != name {

					if y == "" {
						continue
					}

					if y == v {
						s, err := os.Stat(y)
						if err == nil {
							if s.IsDir() {
								continue
							}
						}

						fmt.Println("warning: conflicting file:", _x, y, name, v)
					}
				}
			}
		}
	}
}


func CompressSource(name string, vers string, grp string, sys string) {

	GrpDir := filepath.Join(SysPkgDir, grp)
	PkgDir := filepath.Join(TmpDir, "pkg", name)
	FileList := filepath.Join(PkgDir, "filelist")

	fmt.Println("      writing filelist")

	os.Chdir(PkgDir)

	filepath.Walk(".", WriteFileList)
	filelist := ReadFile(FileList)

	os.Remove(FileList)

	_, err := os.Stat(GrpDir)
	if err != nil {
		os.Mkdir(GrpDir, 0755)
	}

	pkgs, _ := filepath.Glob(filepath.Join(GrpDir, name) + "-[0-9]*")
	for _, pkg := range pkgs {
		args := []string{"-f", pkg}
		Run("rm", args...)
	}

	fmt.Println("      compressing file")

	pkgfile := name + "-" + vers + "-" + PkgFileExt

	compress := []string {"tar", "-cJf", filepath.Join(GrpDir, pkgfile), "./"}  

	Run("fakeroot", compress...)

	CheckFileLists(filelist, name)

	InsertDataBaseEntryPkg(GlobalSQLite, name, vers, grp, filelist, sys)

}


func CleanBuildEnv(name string) {

	PkgDir := filepath.Join(TmpDir, "pkg", name)
	SrcDir := filepath.Join(TmpDir, "src", name)

	os.RemoveAll(PkgDir)
	os.RemoveAll(SrcDir)

}


func CreateDataBase(x string, SQLfile string) {

	_, err := os.Stat(SQLfile)

	if err != nil {

		db, err := sql.Open("sqlite3", SQLfile)
		if err != nil {
			fmt.Println(err)
			return
		}
		defer db.Close()

		if x == "src" {
			sql := "create table src (name text primary key, vers text, grp text, url text, path text, sys text)"
			_, err = db.Exec(sql)
			if err != nil {
				fmt.Printf("%q: %s\n", err, sql)
				return
			}
		} else if x == "pkg" {
			sql := "create table pkg (name text primary key, vers text, grp text, list text, sys text)"
			_, err = db.Exec(sql)
			if err != nil {
				fmt.Printf("%q: %s\n", err, sql)
				return
			}
		}
	}
}


func InsertDataBaseEntryPkg(SQLfile string, name string, vers string, grp string, list string, sys string) {

	CreateDataBase("pkg", SQLfile)

	db, _ := sql.Open("sqlite3", SQLfile)

	tx, err := db.Begin()
	if err != nil {
		fmt.Println(err)
		return
	}

	stmt, err := tx.Prepare("select name from pkg where name=?")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer stmt.Close()

	var _name string
	err = stmt.QueryRow(name).Scan(&_name)
	if err == nil {

		_, err = tx.Exec("delete from pkg where name='" + _name + "'")
		if err != nil {
			fmt.Println(err)
			return
		}
	}

	stmt, err = tx.Prepare("insert into pkg(name, vers, grp, list, sys) values(?, ?, ?, ?, ?)")

	if err != nil {
		fmt.Println(err)
		return
	}
	defer stmt.Close()

	_, err = stmt.Exec(name, vers, grp, list, sys)
	if err != nil {
		fmt.Println(err)
		return
	}

	tx.Commit()

}


func InsertDataBaseEntrySrc(SQLfile string, name string, vers string,
			grp string, url string, path string, sys string) {

	CreateDataBase("src", SQLfile)

	db, _ := sql.Open("sqlite3", SQLfile)

	tx, err := db.Begin()
	if err != nil {
		fmt.Println(err)
		return
	}

	stmt, err := tx.Prepare("select name from src where name=?")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer stmt.Close()

	var _name string
	err = stmt.QueryRow(name).Scan(&_name)
	if err == nil {

		_, err = tx.Exec("delete from src where name='" + _name + "'")
		if err != nil {
			fmt.Println(err)
			return
		}
	}

	stmt, err = tx.Prepare("insert into src(name, vers, grp, url, path, sys) values(?, ?, ?, ?, ?, ?)")

	if err != nil {
		fmt.Println(err)
		return
	}
	defer stmt.Close()

	_, err = stmt.Exec(name, vers, grp, url, path, sys)
	if err != nil {
		fmt.Println(err)
		return
	}

	tx.Commit()

}


func EraseDataBaseEntry(x string, SQLfile string, name string) {

	db, _ := sql.Open("sqlite3", SQLfile)

	tx, err := db.Begin()
	if err != nil {
		fmt.Println(err)
		return
	}

	stmt, err := tx.Prepare("select name from " + x + " where name=?")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer stmt.Close()

	var _name string
	err = stmt.QueryRow(name).Scan(&_name)
	if err == nil {

		_, err = tx.Exec("delete from " + x + " where name='" + _name + "'")
		if err != nil {
			fmt.Println(err)
			return
		}
	}

	tx.Commit()

}


func UpdateSysSrc(SQLfile string, name string, sys string) {

	db, _ := sql.Open("sqlite3", SQLfile)

	tx, err := db.Begin()
	if err != nil {
		fmt.Println(err)
		return
	}

	stmt, err := tx.Prepare("select name from src where name=?")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer stmt.Close()

	var _name string
	err = stmt.QueryRow(name).Scan(&_name)
	if err == nil {

		_, err = tx.Exec("update src set sys = \"" + sys + "\" where name ='" + _name + "'")
		if err != nil {
			fmt.Println(err)
			return
		}
	}

	tx.Commit()
}


func GetInfoPkg(SQLfile string, name string) [5]string{

	var _name string
	var vers string
	var grp string
	var list string
	var sys string

	db, _ := sql.Open("sqlite3", SQLfile)

	stmt, err := db.Prepare("select name,vers,grp,list,sys from pkg where name = ?")
	if err != nil {
		return [5]string{_name, vers, grp, list, sys}
	}
	defer stmt.Close()

	err = stmt.QueryRow(name).Scan(&_name, &vers, &grp, &list, &sys)
	if err != nil {
		return [5]string{_name, vers, grp, list, sys}
	}

	return [5]string{_name, vers, grp, list, sys}

}


func GetInfoSrc(SQLfile string, name string) [6]string{

	_, err := os.Stat(SQLfile)
	if err != nil {
		FetchRecipe()
	}

	db, _ := sql.Open("sqlite3", SQLfile)

	stmt, err := db.Prepare("select name,vers,grp,url,path,sys from src where name = ?")
	if err != nil {
		fmt.Println("warning:", err)
	}
	defer stmt.Close()

	var _name string
	var vers string
	var grp string
	var url string
	var path string
	var sys string

	err = stmt.QueryRow(name).Scan(&_name, &vers, &grp, &url, &path, &sys)
	if err != nil {
		fmt.Println("warning:", err)
	}

	path = strings.Replace(path, "%name", _name, -1)
	path = strings.Replace(path, "%vers", vers, -1)

	return [6]string{_name, vers, grp, url, path, sys}

}


func GetPkgInfo(arg string) map[string]string {

	CreateDataBase("pkg", GlobalSQLite)

	db, _ := sql.Open("sqlite3", GlobalSQLite)

	rows, err := db.Query("select name, " +  arg + " from pkg")

	if err != nil {
		fmt.Println(err)
		return map[string]string {}
	}

	defer rows.Close()

	pkglist := map[string]string{}

	for rows.Next() {
		var name string
		var x string
		rows.Scan(&name, &x)
		pkglist[name] = x
	}

	rows.Close()

	return pkglist

}


func GetSrcInfo(SQLfile string) {

	_, err := os.Stat(SQLfile)
	if err != nil {
		FetchRecipe()
	}

	db, _ := sql.Open("sqlite3", SQLfile)

	rows, err := db.Query("select name,vers,grp,url,path from src")

	if err != nil {
		fmt.Println(err)
	}

	defer rows.Close()

	for rows.Next() {
		var name string
		var vers string
		var grp string
		var url string
		var path string
		rows.Scan(&name, &vers, &grp, &url, &path)
		fmt.Println(name, vers, grp, url, path)
	}

	rows.Close()
}


func ReadSysFile(file string, sysfunc string) bool{

	f, err := os.Open(file)
	CheckError(err)

	defer f.Close()

	r := bufio.NewReader(f)
	line, err := r.ReadString('\n')

	if strings.Contains(line, sysfunc) {
		return true
	}

	for err == nil {
		line, err = r.ReadString('\n')

		if strings.Contains(line, sysfunc) {
			return true
		}
	}

	if err != io.EOF {
		fmt.Println(err)
	}

	return false
}


func WriteSysFile(name string, sys string, TmpFile string) {
	
	lines := strings.Split(sys, "\n")

	w, err := os.OpenFile(TmpFile, os.O_WRONLY | os.O_APPEND | os.O_CREATE, 0644)
	CheckError(err)

	for _, line := range lines {

		_, err = w.Write([]byte(line + "\n"))
		CheckError(err)
	}

	defer w.Close()

}


func RunSysFile(sysfunc string, name string, sys string) {

	TmpFile, err := ioutil.TempFile(os.TempDir(), name + SysFileExt)
	CheckError(err)

	WriteSysFile(name, sys, TmpFile.Name())

	os.Chdir(RootDir)
	args := []string{"-i", "-c", ". " + TmpFile.Name() + "; if [ \"$(type -t "+ sysfunc + ")\" = 'function' ]; then " + sysfunc + "; fi"}
	Run("bash", args...)
	os.Chdir("../")

	os.Remove(TmpFile.Name())
}


func InstallPackage(name string, root string, syscfg bool) {

	if root != "" {
		RootDir = root
	}

	vers := GetInfoPkg(GlobalSQLite, name)[1]
	grp := GetInfoPkg(GlobalSQLite, name)[2]
	sys := GetInfoPkg(GlobalSQLite, name)[4]

	if syscfg == false {
		fmt.Println("     ", name + "-" + vers)
	}

	if syscfg == true {
		fmt.Println("info: installing", name + "-" + vers)

		if sys != "" {

			RunSysFile(PkgSyncSetup, name, sys)
		}
	}

	pkgfile := name + "-" + vers + "-" + PkgFileExt

	pkgpath := filepath.Join(SysPkgDir, grp, pkgfile)

	decompress := []string{"-C", RootDir, "-xf", pkgpath}

	_, err := os.Stat(pkgpath)
	if err == nil {

		Run("tar", decompress...)
	}

	filelist := GetInfoPkg(GlobalSQLite, name)[3]

	if root != "" {

		_, err := os.Stat(filepath.Join(RootDir, SysDir))
		if err != nil {
			os.MkdirAll(filepath.Join(RootDir, SysDir), 0755)
		}

		LocalSQLite := filepath.Join(RootDir, LocalSQLite)

		InsertDataBaseEntryPkg(LocalSQLite, name, vers, grp, filelist, sys)

	} else {

		_, err := os.Stat(SysDir)
		if err != nil {
			os.MkdirAll(SysDir, 0755)
		}

		InsertDataBaseEntryPkg(LocalSQLite, name, vers, grp, filelist, sys)
	}

	if syscfg == true {
		if sys != "" {
			RunSysFile(PkgSync, name, sys)
		}

		fmt.Println("      done")
	}
}



func InstallPackageGroup(grp string, root string, syscfg bool) {

	if root == "" {
		prepare := []string {"sync-prepare", grp, root}
		Run("syscfg", prepare...)
	}

	fmt.Println("info: installing package(s):")

	if grp == "infra" {
		InstallPackage("filesystem", root, syscfg)
	}

	for pkgname, pkggrp := range GetPkgInfo("grp") {

		if pkggrp == grp {

			if pkgname == "filesystem" {
				continue
			}

			InstallPackage(pkgname, root, syscfg)
		}
	}

	fmt.Println("      done")

	if root == "" {
		arrange := []string {"sync-arrange", grp, root}
		Run("syscfg", arrange...)
	}
}


func SplitBase(path string) string { 

	ext := filepath.Ext(path)
	if ext != "" {
		base := filepath.Base(path)
		root := path[:len(path)-len(base)] 
		return root
	}

	return path
} 


func RemovePackage(name string, root string, syscfg bool) {

	if root != "" {
		RootDir = root
	}

	LocalSQLite := filepath.Join(RootDir, LocalSQLite)

	vers := GetInfoPkg(LocalSQLite, name)[1]
	filelist := GetInfoPkg(LocalSQLite, name)[3]
	sys := GetInfoPkg(LocalSQLite, name)[4]

	if syscfg == false {
		fmt.Println("     ", name + "-" + vers)
	}

	if syscfg == true {
		fmt.Println("info: removing package:", name + "-" + vers)

		if sys != "" {
			RunSysFile(PkgFree, name, sys)
		}
	}

	if len(filelist) == 0 {
		fmt.Println("warning: missing filelist:", name)
		return
	}

	f := strings.Split(filelist, "\n")

	for _, t := range f {

		if t != "" {

			t = filepath.Join(RootDir, t)

			l, _ := os.Lstat(t)

			if l.Mode() & os.ModeSymlink == os.ModeSymlink {
				//fmt.Println("      removing", t)
				Run("unlink", t)
				continue
			}

			s, _ := os.Stat(t)

			if !s.IsDir() {
				//fmt.Println("      removing", t)
				Run("rm", t)

			}
		}
	}

	for _, t := range f {
		t = filepath.Join(RootDir, t)

		if t != "" {

			if SplitBase(t) != "" {
				r := SplitBase(t)
				_, err := os.Stat(r)

				if err == nil {
					if r == "/" {
						continue
					}

					//fmt.Println("      removing", r)
					arg := []string{"-p", "--ignore-fail-on-non-empty", r}
					Run("rmdir", arg...)
					continue
				}
			}
		}
	}

	if syscfg == true {
		if sys != "" {
			RunSysFile(PkgFreeClean, name, sys)
		}

		EraseDataBaseEntry("pkg", LocalSQLite, name)

		fmt.Println("      done")
	}
}


func RemovePackageGroup(grp string, root string, syscfg bool) {

	prepare := []string {"free-prepare", grp, root}
	Run("syscfg", prepare...)

	fmt.Println("info: removing package(s):")

	for pkgname, pkggrp := range GetPkgInfo("grp") {

		if pkggrp == grp {

			RemovePackage(pkgname, root, syscfg)
		}
	}

	fmt.Println("      done")

	release := []string {"free-release", grp, root}
	Run("syscfg", release...)
}
